#import "SolarSeekApplication.h"
#import "ChatsController.h"
#import "Connections.h"
#import "FSelectorWrapper.h"
#import "PreferencesController.h"
#import "ServerConnectionController.h"
#import "SearchesView.h"
#import "ShareController.h"
#import "SourceListElement.h"
#import "SolarSeekEngine.h"
#import "SourceListElementDownloads.h"
#import "SourceListElementNetwork.h"
#import "SolarSeekValueTransformers.h"
#import "TransfersController.h"
#import "ApplicationDefaults.h"
#import "PeersController.h"
#import "Peers.h"
#import "TransferView.h"
#import "ChatsView.h"
#import "FriendsView.h"
#import <Security/Security.h>
#import "PeerInfoWindowController.h"

int LogTypeNone=0; 
int LogTypeAll=-1;
int LogTypeLoud=1; 
int	LogTypeError=2; 
int	LogTypeServer=4; 
int	LogTypePeer=8;
int	LogTypeLoad=16;
int	LogTypeSearch=32;
int	LogTypeTransfer=64;
int	LogTypeShare=128;


typedef enum { 
	ObservingConnection=1, 
	ObservingServerMessageSent, ObservingServerMessageRecieved, ObservingChildren
} SolarSeekApplicationObservingTypes;


#pragma mark -

@interface SolarSeekApplication (Private)
- (NSAttributedString *)attributedStringFromHTML:(NSString *)html;
- (void)installIdentifiersColumnSourceList;
- (void)openHomepage:(id)sender;
@end

@implementation SolarSeekApplication

+ (void)initialize {
	//install value transformers and then install preference defaults.
	
	//Install value transformers
	id o = [[[SolarSeekValueTransformers alloc] init] autorelease];
	[o install];
	NSString *dir = [@"~/Library/Application Support/SolarSeek" stringByExpandingTildeInPath];
	if([[NSFileManager defaultManager] fileExistsAtPath:dir] == NO && [[NSFileManager defaultManager] createDirectoryAtPath:dir attributes:nil]==NO) {
		NSLog(@"Could not create %@.  Errors will result",dir);
	}
	
}

- (void)dealloc {
	[_engine removeObserver:self forKeyPath:@"serverConnectionController.loggedIn"];
	[super dealloc];
}

- (void)didDecide:(NSAlert *)win returnCode:(int)code context:(void *)context {
	if(code == NSAlertOtherReturn) {
		[NSApp replyToApplicationShouldTerminate:NO];
	}
	else {
		if(code==NSAlertAlternateReturn) 
			[[NSUserDefaults standardUserDefaults] setBool:NO forKey:SolarSeekDefaultPromptBeforeQuittingWithActiveDownloads];
		[NSApp replyToApplicationShouldTerminate:YES];
	}
}

- (void)decideWhetherToTerminate {
	NSSet *transfers = [[[self engine] transfersController] transfersWithType:TransferTypeDownload];
	NSEnumerator *e = [transfers objectEnumerator];
	Transfer *t;
	unsigned int count=0;
	while(t = [e nextObject] ) {
		if([t state]==TransferStateTransferring || [t state]==TransferStateStarting ||
			[t state]==TransferStateIdle) {
			count++;
		}
	}
	[nibWindowMain makeKeyAndOrderFront:self];
	[[NSAlert alertWithMessageText:@"Downloads still active" defaultButton:@"Quit Now" alternateButton:@"Stop Asking Me" otherButton:@"Quit Later" informativeTextWithFormat:@"You have %d active transfers.  These transfers cannot finish if you quit now.  Are you sure that you want to quit and cancel these transfers?",count]
		beginSheetModalForWindow:nibWindowMain modalDelegate:self didEndSelector:@selector(didDecide:returnCode:context:) contextInfo:nil];
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
	if([[NSUserDefaults standardUserDefaults] boolForKey:SolarSeekDefaultPromptBeforeQuittingWithActiveDownloads]==NO)
		return NSTerminateNow;
	NSSet *transfers = [[[self engine] transfersController] transfersWithType:TransferTypeDownload];
	NSEnumerator *e = [transfers objectEnumerator];
	Transfer *t;
	while(t = [e nextObject] ) {
		if([t state]==TransferStateTransferring || [t state]==TransferStateStarting ||
			[t state]==TransferStateIdle) {
			[self decideWhetherToTerminate];
//			[self performSelector:@selector(decideWhetherToTerminate) withObject:nil afterDelay:0.0];
			return NSTerminateLater;
		}
	}
	return NSTerminateNow;
}	

- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender {
	return YES;
}

- (void)applicationOpenUntitledFile:(NSApplication *)sender {
	[nibWindowMain makeKeyAndOrderFront:self];
}

- (id)init {
	self = [super init];
	if(self)
		finishedLaunching = NO;
	return self;
}

- (BOOL)finishedLaunching { return finishedLaunching; }

- (void)awakeFromNib {
	finishedLaunching = NO;

	[self setDelegate:self];
	
	_elementCurrent = nil;
	
	_toolbar = [[NSToolbar alloc] initWithIdentifier:@"com.fejta.SolarSeek.toolbar.main"];
	[_toolbar setDelegate:self];
	[nibWindowMain setToolbar:_toolbar];
	
	[self willChangeValueForKey:@"engine"];
	_engine = [[SolarSeekEngine alloc] init];
	[self didChangeValueForKey:@"engine"];
	
//	[nibSourceList setIndentationPerLevel:2.0];

	//ROOT ELEMENTS SETUP
	FOutlineViewArrayElement *fovae = [nibSourceList root];
	[fovae addElement:[[[SourceListElementNetwork alloc] initWithView:nibViewNetwork] autorelease]];
	[nibViewDownloads setTransferType:TransferTypeDownload];
	[fovae addElement:[[[SourceListElementDownloads alloc] initWithView:nibViewDownloads] autorelease]];
	[nibViewUploads setTransferType:TransferTypeUpload];
	[fovae addElement:[[[SourceListElementDownloads alloc] initWithView:nibViewUploads] autorelease]];	
	[_engine addObserver:self forKeyPath:@"serverConnectionController.loggedIn" options:nil context:(void *)ObservingConnection];
	[fovae addElement:[nibViewChatRooms element]];
	[fovae addElement:[nibViewChats element]];
	[fovae addElement:[nibViewFriends element]];
	[fovae addElement:[nibViewSearches element]];
	
	//	STATIC ROOT ELEMENTS SETUP
	/*
	fovae = (FOutlineViewArrayElement *)[nibSourceListStatic root];
	[fovae addElement:[[[SourceListElementNetwork alloc] initWithView:nibViewNetwork] autorelease]];
	[nibViewDownloads setTransferType:TransferTypeDownload];
	[fovae addElement:[[[SourceListElementDownloads alloc] initWithView:nibViewDownloads] autorelease]];
	[nibViewUploads setTransferType:TransferTypeUpload];
	[fovae addElement:[[[SourceListElementDownloads alloc] initWithView:nibViewUploads] autorelease]];	
	[_engine addObserver:self forKeyPath:@"serverConnectionController.loggedIn" options:nil context:(void *)ObservingConnection];
	[fovae addElement:[nibViewFriends element]];
	[fovae addElement:[nibViewChatRooms element]];
	*/
	
	[nibTableResults setTarget:self];
	[nibTableResults setDoubleAction:@selector(downloadShares:)];
	
	[self updateSourceList];
	
	if([[NSUserDefaults standardUserDefaults] boolForKey:SolarSeekDefaultConnectOnStartup]) {
		[self performSelector:@selector(logInToServer:) withObject:self afterDelay:0.0];
	}
	
	[self performSelector:@selector(selectSourceListElement:) withObject:[self sourceListElementForView:nibViewNetwork] afterDelay:0.0];
	
	[self checkForUpdates];
	@synchronized(self) {
		finishedLaunching = YES;
	}
}

#pragma mark actions (void)

- (void)appendLogWithHTML:(NSString *)html type:(LogType)type {
	@synchronized([self log]) {
		//fejta: TODO: low: enable,disable various log types.
		void *chars = (void *)[html UTF8String];
		unsigned len = strlen(chars);
		NSData *d = [[NSData alloc] initWithBytes:chars length:len];
		NSDictionary *dict=nil;
		if(d) {
			NSAttributedString *a = [[NSAttributedString alloc] initWithHTML:d documentAttributes:&dict];
			[d release];
			if(a) {
				[[self log] appendAttributedString:[[[NSAttributedString alloc] initWithString:@"\n"] autorelease]];
				NSTextStorage *t = [self log];
				unsigned al = [a length], sl = [t length];
				if(al + sl > 10101) { //the 1 is for the return character.
					NSRange range = NSMakeRange(0,al+sl - 10000);
					[t deleteCharactersInRange:range];
				}
				[t appendAttributedString:a];
			}	
			[a release];
		}
	}
}

- (void)appendLog:array {
	NSString *header = [array objectAtIndex:0];
	NSString *html = [array objectAtIndex:1];
	int type = [[array objectAtIndex:2] intValue];
	NSString *string = [[NSString alloc] initWithFormat:@"<B>%@</B>: %@",header,html];
	[self appendLogWithHTML:string type:type];
	[string release];
}

- (void)appendLogWithHeader:(NSString *)header HTML:(NSString *)html type:(LogType)type {
	NSArray *array = [NSArray arrayWithObjects:header,html,[NSNumber numberWithInt:type],nil];
	[self performSelectorOnMainThread:@selector(appendLog:) withObject:array waitUntilDone:NO];
}



- (void)installIdentifiersColumnSourceList {
	NSArray *a = [nibSourceList tableColumns];
	unsigned column = 0;
	[(NSTableColumn *)[a objectAtIndex:column++] setIdentifier:[SelectorWrapper wrapSelector:@selector(icon)]];
	[(NSTableColumn *)[a objectAtIndex:column++] setIdentifier:[SelectorWrapper wrapSelector:@selector(name)]];
	[(NSTableColumn *)[a objectAtIndex:column++] setIdentifier:[SelectorWrapper wrapSelector:@selector(buttons)]];
}

- (IBAction)toggleAway:(id)sender {
	[[[NSApp engine] me] retain];
	Availability avail = [[[NSApp engine] me] availability];
	if(avail != AvailabilityAway) {
		avail = AvailabilityAway;
	}
	else
		avail = [[[NSApp engine] serverConnectionController] loggedIn]?AvailabilityOnline:AvailabilityOffline;
	[[NSApp engine] setAvailability:avail];
	[nibMenuAway setState:avail==AvailabilityAway?NSOnState:NSOffState];
}

- (IBAction)infoWindowForSelectedPeers:(id)sender {
	NSEnumerator *e = nil;
	if([[[NSApp selectedSourceListElement] view] respondsToSelector:@selector(participantsSelected)])
		e = [[[[NSApp selectedSourceListElement] view] participantsSelected] objectEnumerator];
	Peer *p;
	while(p = [e nextObject]) {
		[self getInfoWindowForPeer:p];
	}
}
- (void)getInfoWindowForPeer:(Peer *)peer {
	[PeerInfoWindowController showInfoForPeer:peer];
}

- (IBAction)getBuddyIconForSelectedPeers:(id)sender {
	NSEnumerator *e = nil;
	if([[[NSApp selectedSourceListElement] view] respondsToSelector:@selector(participantsSelected)])
		e = [[[[NSApp selectedSourceListElement] view] participantsSelected] objectEnumerator];
	Peer *p;
	while(p = [e nextObject]) {
		[[NSApp engine] getBuddyIconForPeer:p];
	}
}

- (void)logInToServer:(id)sender {

	switch([nibMenuLogIn state]) {
	case NSOffState: {
			NSString *u = [[NSUserDefaults standardUserDefaults] stringForKey:SolarSeekDefaultUsername];
			if(u==nil || [u isEqual:@""]) {
				[nibPreferencesController setupUsername];
			}
			else {
				NSString *host = [[NSUserDefaults standardUserDefaults] stringForKey:SolarSeekDefaultServerName];
				UInt16 port = [[NSUserDefaults standardUserDefaults] integerForKey:SolarSeekDefaultPortServer];
				NS_DURING
					[[_engine serverConnection] connectToHost:host onPort:port];
					[self appendLogWithHTML:@"<B>CONNECTION:</B> Connecting..." type:LogTypeServer];
					[nibMenuLogIn setState:NSMixedState];
				NS_HANDLER
					[[NSAlert alertWithMessageText:@"Connection Error" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"Unable to connect to %@:%d.  Please check your network settings and try again.",host,port] beginSheetModalForWindow:[NSApp keyWindow] modalDelegate:nil didEndSelector:nil contextInfo:nil];
				NS_ENDHANDLER 
			}
		}
		break;
	case NSMixedState:
		[nibMenuLogIn setState:NSOffState];
		//do not break
	default:
		[[_engine serverConnection] disconnect];
	}
}

- (IBAction)downloadFolder:(id)sender {
	SharePeer *p = [[nibControllerShares selectedObjects] lastObject];
	[[_engine transfersController] downloadFolderOfShare:p];
	if([[NSUserDefaults standardUserDefaults] boolForKey:SolarSeekDefaultSwitchToDownloadViewWhenStarting]) {
		[self selectSourceListElement:[self sourceListElementForView:nibViewDownloads]];
	}
}

- (IBAction)downloadShares:(id)sender {
	NSEnumerator *e = [[nibControllerShares selectedObjects] objectEnumerator];
	SharePeer *p;
	while(p = [e nextObject]) {
		[[_engine transfersController] downloadShare:p];
	}
	if([[NSUserDefaults standardUserDefaults] boolForKey:SolarSeekDefaultSwitchToDownloadViewWhenStarting]) {
		[self selectSourceListElement:[self sourceListElementForView:nibViewDownloads]];
	}
}

- (IBAction)openForum:(id)sender {
	[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://solarseek.sourceforge.net/forum/"]];
}

- (IBAction)openHomepage:(id)sender {
	[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://solarseek.sourceforge.net/"]];
}

- (IBAction)refreshUserStatistics:(id)sender {
	NSArray *a = [[self selectedSourceListElement] participants];
	[[_engine peersController] refreshInformationForPeers:a];
}

- (void)revealStrings:(NSArray *)array {
	NSString *s;
	NSEnumerator *e = [array objectEnumerator];
	while(s = [e nextObject]) {
		[self revealString:s];
	}
}

- (void)revealString:(NSString *)string {
	NSString *file = [string stringByExpandingTildeInPath];
	NSString *dir = [file stringByDeletingLastPathComponent];
	BOOL b = [[NSWorkspace sharedWorkspace] selectFile:file inFileViewerRootedAtPath:file];
}



- (void)revealDownloadFolder:(id)sender {
	[self revealString:[[NSUserDefaults standardUserDefaults] stringForKey:SolarSeekDefaultPathDownloads]];
}

- (void)revealIncomingDownloadFolder:(id)sender  {
	[self revealString:[[NSUserDefaults standardUserDefaults] stringForKey:SolarSeekDefaultPathDownloadsIncomplete]];
}


- (void)updateSourceList {
	@synchronized(self) {
		[nibSourceList reloadData]; 
		[nibSourceListStatic reloadData];
	}
}

- (void)updateSourceListElement:(id<SourceListElement>)element {
	[self updateSourceListElement:element children:NO];
}

- (void)updateSourceListElement:(id<SourceListElement>)element children:(BOOL)children {
	if(children && [element expandable] && [[element children] count]) {
		[nibSourceList reloadItem:element reloadChildren:YES];
		[nibSourceListStatic reloadItem:element reloadChildren:YES];
	}
	else {
		[nibSourceList reloadItem:element];
		[nibSourceListStatic reloadItem:element];
	}
}

-(void) checkForUpdates {
	// TODO: fix the following codde. Actually, the app just check the date, but should check the site for a new version. I can do it, but as a 10.3+ only feature (using NSURLConnection).
	// NSDate *expire = [NSDate dateWithString:@"2006-02-01 10:10:10 +0600"];
	// NSDate *now = [NSDate date];
	// if([expire laterDate:now] == now)
		// [self openHomepage:self];
}

#pragma mark Observing

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	switch((int)context) {
		case ObservingConnection: {
			if([[_engine serverConnectionController] loggedIn]) {
				[[[NSApp engine] chatsController] chatWithRoom:[[[NSApp engine] chatsController] roomNamed:@"SolarSeek"] autojoin:YES];
				[nibMenuLogIn setTitle:@"Log out of Server"];
				[nibMenuLogIn setState:NSOnState];
			}
			else {
				if([nibMenuLogIn state]==NSMixedState) {
					NSString *error = [[_engine serverConnectionController] messageError];
					NSString *text = @"Could not log in";
					if(error == nil || [error isEqual:@""]) {
						text = @"Could not Connect";
						error = @"The server could not be reached.  This most likely means that the server is down, you are having local connection problems, or the server banned you for flooding the server.  Please wait a few minutes and try again.";
					}
					else if([error isEqual:@"INVALIDPASS"]) {
						[nibPreferencesController setupPassword];
						text = @"Invalid Username/Password";
						error = @"The server rejected your username and password combination.  This most likely means that you entered your password incorrectly or are trying to use a username that already exists.  Please correct your password and try again.  If you continue to experience this trouble or have forgotten your password, please choose a new username.";
						
					}
					else {
						error = [@"The server refused to log you in and provided the following reason: " stringByAppendingString:error];
					}
					[[NSAlert alertWithMessageText:text defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:error] beginSheetModalForWindow:[NSApp keyWindow] modalDelegate:nil didEndSelector:nil contextInfo:nil];
				}
				[nibMenuLogIn setTitle:@"Log in to Server"];
				[nibMenuLogIn setState:NSOffState];
			}
			break;
		}
		default:
			[NSException raise:@"InvalidObservingException" format:@"Learn how to observe this."];
	}
	
}

#pragma mark OutlineView (source list)
- (void)expandSourceListElement:(id<SourceListElement>)sourceListElement {
	@synchronized(self) {
	//automatically change selection under certain conditions
		[nibSourceList expandItem:sourceListElement];
	}
}

- (void)selectSourceListRow:(unsigned)row staticList:(BOOL)isstatic {
	FSourceListView *view = isstatic?nibSourceListStatic:nibSourceList;
	NSNotification *note = [NSNotification notificationWithName:@"Manual Change" object:view];
	[self outlineViewSelectionIsChanging:note];
	[view selectRow:row byExtendingSelection:NO];
	[self outlineViewSelectionDidChange:note];
}

- (void)selectSourceListElement:(id<SourceListElement>)select {
	@synchronized(self) {
		int row = [nibSourceList rowForItem:select];
		BOOL s = NO;
		if(s = row==-1)
			row = [nibSourceListStatic rowForItem:select];
		if(row!=-1)
			[self selectSourceListRow:row staticList:s];
	}
}

- (void)outlineViewSelectionIsChanging:(NSNotification *)note {
	if([note object] == nibSourceList)
		[nibSourceListStatic deselectAll:self];
	else
		[nibSourceList deselectAll:self];
}

- (void)change {
		[_elementCurrent setSelected:YES];
		[self updateSourceListElement:_elementCurrent];
}

- (void)outlineViewSelectionDidChange:(NSNotification *)note {
	//change the content view.
	int row = [nibSourceList selectedRow];
	SourceListElement *sle = nil;
	if(row >= 0) {
		sle = [nibSourceList itemAtRow:row];
	}
	else if ((row = [nibSourceListStatic selectedRow]) >= 0) {
		sle = [nibSourceListStatic itemAtRow:row];
	}
	
	if(sle && sle != _elementCurrent) {
		[self willChangeValueForKey:@"elementCurrent"];
		[_elementCurrent setSelected:NO];
		_elementCurrent = sle;
		[self didChangeValueForKey:@"elementCurrent"];
		[self change];
//		[self performSelector:@selector(change) withObject:nil afterDelay:0.01];
	}
}

//
// Data Source Protocol
//

- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
	//return the object representing the row to display
	NSAssert(outlineView == nibSourceList, @"Wrong outline view.");
	NSAssert(item==nil || [item conformsToProtocol:@protocol(SourceListElement)],@"item must conform to the SourceListElement protocol.");
	SourceListElement *sle = item?item:self;
	return [[sle children] objectAtIndex:index];
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
	//return true if we can expand this item
	NSAssert(outlineView == nibSourceList, @"Wrong outline view.");
	NSAssert([item conformsToProtocol:@protocol(SourceListElement)],@"item must conform to the SourceListElement protocol.");
	SourceListElement *sle = item;
	BOOL b = [sle expandable];
	return b;
}

- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
	if(nibSourceList==nil)
		return 0;
	NSAssert(outlineView == nibSourceList, @"Wrong outline view.");
	NSAssert(item==nil || [item conformsToProtocol:@protocol(SourceListElement)],@"item must conform to the SourceListElement protocol.");
	SourceListElement *sle = item?item:self;
	return [[sle children] count];
}

- (id)outlineView:(NSOutlineView *)outlineView 
	objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
	//return the object representing the column given the row item
	NSAssert(outlineView == nibSourceList, @"Wrong outline view.");
	NSAssert([item conformsToProtocol:@protocol(SourceListElement)],@"item must conform to the SourceListElement protocol.");
	NSAssert([[tableColumn identifier] isKindOfClass:[SelectorWrapper class]],@"Table column's identifier is not a selector instance.");

	SourceListElement *sle = item;
	return [sle performSelector:[(SelectorWrapper *)[tableColumn identifier] selector]];
}

#pragma mark Toolbar

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
	//return an array with strings of default items in toolbar.
	NSAssert(toolbar == _toolbar,@"Wrong toolbar.");
	return [NSArray arrayWithObjects:@"Search",NSToolbarFlexibleSpaceItemIdentifier,@"Download",NSToolbarSeparatorItemIdentifier,@"Browse",@"Chat",@"Information",nil];
}

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
	//return an array of strings of possible items in toolbar.
	NSAssert(toolbar == _toolbar,@"Wrong toolbar.");
	return [self toolbarDefaultItemIdentifiers:toolbar];
}

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)identifier
	willBeInsertedIntoToolbar:(BOOL)flag
{
	NSAssert(toolbar == _toolbar,@"Wrong toolbar.");
	NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:identifier] autorelease];
	[item setLabel:identifier];
	[item setImage:[NSImage imageNamed:[@"toolbar" stringByAppendingString:identifier]]];
	if([identifier isEqual:@"Browse"]) {
		[item setToolTip:@"Browse all of this person's files"];
		[item setTarget:nibViewSearches];
		[item setAction:@selector(browseSelectedPeers:)];
	}
	else if([identifier isEqual:@"Download"]) {
		[item setToolTip:@"Download the selected files"];
		[item setTarget:self];
		[item setAction:@selector(downloadShares:)];
	}
	else if([identifier isEqual:@"Chat"]) {
		[item setTarget:nibViewChats];
		[item setToolTip:@"Start a private chat with the selected people"];
		[item setAction:@selector(startPrivateChats:)];
	}
	else if([identifier isEqual:@"Search"]) {
		[item setLabel:@"Search"];
		[item setToolTip:@"Search for files on the soulseek network."];
		[item setView:nibViewToolbarSearch];
		[item setMinSize:NSMakeSize(150,NSHeight([nibViewToolbarSearch frame]))];
		[item setMaxSize:NSMakeSize(220,NSHeight([nibViewToolbarSearch frame]))];
	}
	else if([identifier isEqual:@"Information"]) {
		[item setLabel:@"Get Info"];
		[item setToolTip:@"Get more information about the selected people"];
		[item setTarget:self];
		[item setAction:@selector(infoWindowForSelectedPeers:)];
	}
	else {
		[NSException raise:@"UnknownItemException" format:@"We cannot create a %@",identifier];
		return nil;
	}
	return item;

}

#pragma mark Splitter

- (float)splitView:(NSSplitView *)sender constrainMinCoordinate:(float)proposedMin ofSubviewAt:(int)offset {
	return 120;
}

- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize {
	NSSize newSize = [sender frame].size;
	
	float delta = newSize.width - oldSize.width;
	
	float thick = [sender dividerThickness];
	
	NSRect sourcelist = [[[sender subviews] objectAtIndex:0] frame];
	NSRect view = [[[sender subviews] objectAtIndex:1] frame];
	
	view.size.width += delta;
	
	if(view.size.width < 100) {
		sourcelist.size.width -= view.size.width;
		view.size.width = 100;
	}
	
	sourcelist.size.height = view.size.height = newSize.height;

	[[[sender subviews] objectAtIndex:0] setFrame:sourcelist];
	[[[sender subviews] objectAtIndex:1] setFrame:view];
}


#pragma mark KVO
- (NSString *)password {
	OSStatus rc;
	SecKeychainItemRef keychainItem;
	UInt32 passwordLength = 128;
	void *password = (void *)malloc(passwordLength * sizeof(char));
	NSString *result = @"";
	rc = SecKeychainFindGenericPassword(NULL,
		[SolarSeekKeychainService length],[SolarSeekKeychainService cString],
		[SolarSeekKeychainItem length],[SolarSeekKeychainItem cString],
		&passwordLength,&password,&keychainItem);

	// Password successfully returned
	if (rc >= 0) 
			result = [NSString stringWithCString:(const char*)password length:passwordLength];        
	else if(rc == errSecItemNotFound) {
		NSString *old = [[NSUserDefaults standardUserDefaults] stringForKey:@"solarseekPassword"];
		if(old && [old isEqual:@""]==NO) {
			[nibPreferencesController performSelector:@selector(setPassword) withObject:nil afterDelay:0.5];
			[[NSUserDefaults standardUserDefaults] setObject:@"" forKey:@"solarseekPassword"];
			return old;
		}
		[nibPreferencesController setupPassword];
		[[NSAlert alertWithMessageText:@"Password not set" 
				defaultButton:@"Open Preferences" alternateButton:nil otherButton:nil 
				informativeTextWithFormat:@"You have not yet set your password.  Please do so in the network preferences."] beginSheetModalForWindow:nibWindowPreferences modalDelegate:nil didEndSelector:nil contextInfo:nil];
	}
	else {
		[[NSAlert alertWithMessageText:@"Could not change password" 
				defaultButton:@"OK" alternateButton:nil otherButton:nil 
				informativeTextWithFormat:@"There was an error while trying to retrieve your password from the security keychain.  The Keychain services returned the following result: %d.",rc] beginSheetModalForWindow:[self keyWindow] modalDelegate:nil didEndSelector:nil contextInfo:nil];
	}
	free(password);
	return result;
}
- (SolarSeekEngine *)engine { return _engine; }
- (NSTextStorage *)log { 
	@synchronized(self) {
		return [nibTextLog textStorage]; 
	}
}
- (SolarSeekEngine *)network { return _engine; }
- (BOOL)requestingRefresh { return NO; }
- (NSArray *)wishesUnimplemented { return [NSArray array]; }

- (id<SourceListElement>)selectedSourceListElement {
	if([nibSourceList selectedRow] >= 0) {
		return [nibSourceList itemAtRow:[nibSourceList selectedRow]];
	}
	else if([nibSourceListStatic selectedRow] >= 0)
		return [nibSourceListStatic itemAtRow:[nibSourceListStatic selectedRow]];
	else
		return nil;
}


- (id<SourceListElement>)sourceListElementForView:(NSView *)view {
	NSEnumerator *e = [[(FOutlineViewArrayElement *)[nibSourceList root] children] objectEnumerator];
	id<SourceListElement> o;
	while(o = [e nextObject]) {
		if([o view] == view)
			return o;
	}
	e = [[(FOutlineViewArrayElement *)[nibSourceListStatic root] children] objectEnumerator];
	while(o = [e nextObject]) {
		if([o view] == view)
			return o;
	}
	return nil;
}

- (NSView *)viewBrowse { return nibViewBrowse; }
- (NSView *)viewChat { return nibViewChat; }
- (NSView *)viewChatRooms { return nibViewChatRooms; }
- (NSView *)viewChats { return nibViewChats; }
- (NSView *)viewFriends { return nibViewFriends; }
- (NSView *)viewNetwork { return nibViewNetwork; }
- (NSView *)viewPrivateChat { return nibViewPrivateChat; }
- (NSView *)viewSearches { return nibViewSearches; }
- (NSView *)viewSearchResults { return nibViewSearchResults; }
- (NSView *)viewWishlists { return nibViewWishlists; }


@end
